package com.android.avy;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.util.Xml;


/**
 * 
 * @author heycosmo
 *
 *  AvyUpdater is a utility class for the Avy app. Its purpose is to
 *  download and parse xml files from the Avy app server that contain 
 *  avalanche forecast data, and weather histories, etc. Once downloaded
 *  and parsed (using the nested class {@link AvyUpdater.AvyXmlHandler}), 
 *  the data is cached on the user's phone in a SQLite database.
 *  
 *  <p>The data is stored on the server (currently a dropbox public folder)
 *  in xml files with names reflecting the date: e.g. 2011-03-02.xml. Within
 *  each file, the structure is:
 *  
 *  <p>
 *  {@code
 *  <zones>
 *  	<zone short_name="shortzonename0">
 *  		<colors>#####</colors>
 *  		<date>yyyy-mm-dd</date>
 *  		<dots>#####</dots>
 *  		<state>CO</state>
 *  		...
 *  	</zone>
 *  	<zone short_name="shortzonename1">
 *  		<colors>#####</colors>
 *  		<date>yyyy-mm-dd</date>
 *  		<dots>#####</dots>
 *  		<state>UT</state>
 *  		...
 *  	</zone>
 *  	...
 *  </zones>
 *  }
 *  
 *  <p>The xml tag of each data block within a <pre><zone></pre> block should match
 *  the name of a column in the database (column names are found in {@link ZoneData}),
 *  otherwise that data block will be ignored.
 */

public class AvyUpdater {
	
    //static public final String SERVER = "http://dl.dropbox.com/u/21263378/avy-app/";
	static public final String SERVER = "http://dl.dropbox.com/u/15668025/";
	
	// This is the interface to the database. You can call query, insert, update methods
	// on this (actually it's an interface to AvyProvider, a subclass of ContentProvider,
	// which is an integral part of the android api)
	private ContentResolver mCR;
	
	/**
	 * Constructor. Needs a ContentResolver instance.
	 * 
	 * @param contentResolver
	 */
	public AvyUpdater(ContentResolver contentResolver) {
		mCR = contentResolver;
	}
	
	/**
	 * Call this function to update the database for the given day. This function
	 * first checks whether the data already exists in the cached SQLite table on
	 * the user's phone. If not, it attempts to download and update that data.
	 * (Currently, this function only checks if zoneday rows exist for a given date.
	 * In the future, if zoneday rows DO exist, we must check that they have been
	 * updated recently, possibly by having a database column that stores
	 * 'last modified' information.)
	 * 
	 * @param day Day for which data needs to be refreshed (format: "yyyy-mm-dd" w/out
	 * the quotes)
	 */
	public void refreshDay(String day) {
		// first check if there exist entries in the content provider for the
		// given day
		Cursor c = mCR.query(ZoneData.ZoneDays.CONTENT_URI,
				new String[] {ZoneData.ZoneDays.COL_NAME_DATE},
				ZoneData.ZoneDays.COL_NAME_DATE + "='" + day + "'",
				null,
				null);
		int count = c.getCount();
		
		// if the entries don't exist, fetch them from the server and
		// insert them using the function defined next
		if (count == 0) {
			insertDay(day);
		}
		
	}
	
	/**
	 * This function executes downloading the xml file and
	 * requesting to insert a new row in the database. Currently (2011/03/14),
	 * this function inserts a new row even if an identical row already exists
	 * (that's why it's private).
	 * 
	 * @param day Day for which data needs to be downloaded and cached. (format:
	 * "yyyy-mm-dd" w/out the quotes)
	 */
	private void insertDay(String day) {
		String urlString = SERVER + day + ".xml";
		
		try {
			AvyXmlHandler handler = new AvyXmlHandler();
            InputStream is = (new URL(urlString)).openConnection().getInputStream();
            
            // parse xml with the handler defined in this class
            Xml.parse(is, Xml.Encoding.UTF_8, handler);
            
            // use the ContentResolver instance to add rows to our database
            for (ContentValues cv : handler.getData()) {
            	mCR.insert(ZoneData.ZoneDays.CONTENT_URI, cv);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
        	e.printStackTrace();
		}
	}
	
	/**
	 * 
	 * @author heycosmo
	 *
	 * This class does the parsing of the Avy server xml files (see introductory
	 * documentation of AvyUpdater class for a description of the server 
	 * xml file structure). For every <pre><zone></pre> block, it creates a new
	 * ContentValues object, which will hold all the data (in the proper type: int,
	 * String, etc.) for the zone. (ContentValues objects are android objects that
	 * are passed to SQLite database insert methods).
	 */
	private class AvyXmlHandler extends DefaultHandler {
	    private StringBuilder builder;
	    private List<ContentValues> listValues;
	    private ContentValues values;
	    
	    public AvyXmlHandler() {
	    	listValues = new ArrayList<ContentValues>();
	    }
	    
	    /**
	     * 
	     * @return A List of ContentValues objects. Each one holds all the data for
	     * a zone
	     */
	    public List<ContentValues> getData() {
	        return listValues;
	    }
	    
	    /**
	     *  This function collects all the character data inside the current xml block
	     *  and stores it in the local stringbuilder.
	     */
	    @Override
	    public void characters(char[] ch, int start, int length) throws SAXException {
	        super.characters(ch, start, length);
	        builder.append(ch, start, length);
	    }

	    /**
	     * Actions at the closure of an xml block.
	     * 
	     * <p>At the end of a <pre><zone></pre> xml block, add the current ContentValues
	     * object to the list. At the end of a zone data block, first use the xml tag
	     * to determine if the block corresponds to a valid database column (xml tag
	     * should be the same as a database column). If so, convert the data to the
	     * proper type and add it to the current ContentValues object.
	     * 
	     */
	    @Override
	    public void endElement(String uri, String localName, String name) throws SAXException {
	        super.endElement(uri, localName, name);
	        
	        // if xml tag is "zone"
	        if (localName.equalsIgnoreCase("zone")) {
	        	listValues.add(values);
	        // else if xml tag matches a column name
	        } else if (ZoneData.ZoneDays.COL_TYPES.containsKey(localName)) {
	        	// if tag matches, get data type for column
	        	String type = ZoneData.ZoneDays.COL_TYPES.get(localName);
	        	if (type.equalsIgnoreCase("int")) {
	        		values.put(localName, Integer.parseInt(builder.toString()));
	        	} else if (type.equalsIgnoreCase("string")) {
	        		values.put(localName, builder.toString());
	        	} else {}
	        } else {}
	        
	        builder.setLength(0);
	    }

	    @Override
	    public void startDocument() throws SAXException {
	        super.startDocument();
	        builder = new StringBuilder();
	    }

	    /**
	     * If we encounter a new <pre><zone></pre> xml block, create a new 
	     * ContentValues object to store zone data.
	     */
	    @Override
	    public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
	        super.startElement(uri, localName, name, attributes);
	        if (localName.equalsIgnoreCase("zone")) {
	            values = new ContentValues();
	        }
	        builder.setLength(0);
	    }
		
	}
}
